package com.auto.api.core.builder;

import com.auto.api.common.utils.JsonMapper;
import com.auto.api.core.constants.MappingConstants;
import com.auto.api.core.entity.ParamNameResolvableType;
import com.auto.api.core.entity.ApiInfo;
import com.auto.api.core.util.BeanUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;

/**
 * @program:
 * @description: 请求项
 * @author: liangshf
 * @create: 2019-07-11 14:04
 */
public class RequestMappingInfoBuilder {

    public Builder instance(String requestUrl, String requestType,
        String controllerName, Method method, ParameterNameDiscoverer parameterUtil) {
        return new RequestMappingInfoBuilder.DefaultBuilder(requestUrl, requestType, controllerName, method,
            parameterUtil);
    }

    public interface Builder {

        Builder buildCommon();

        ApiInfo build();
    }

    private static class DefaultBuilder implements Builder {

        private static final Logger logger = LoggerFactory.getLogger(DefaultBuilder.class);
        protected ApiInfo apiInfo;
        private Method method;
        private ParameterNameDiscoverer parameterUtil;
        private Object[] args;
        private String methodName;
        private String argsStr;


        private DefaultBuilder(String requestUrl, String requestType,
            String controllerName, Method method, ParameterNameDiscoverer parameterUtil) {
            this.method = method;
            this.parameterUtil = parameterUtil;
            this.methodName = method.getName();
            apiInfo = new ApiInfo(requestUrl, requestType, controllerName, methodName);
        }

        /**
         * 获取ControllerInfo 信息
         */
        private Builder controllerInfo() {
            try {
                Class clazz = Class.forName(apiInfo.getControllerName());
                Annotation annotation = clazz.getAnnotation(Api.class);
                if (annotation != null && annotation instanceof Api) {
                    apiInfo.setControllerRmk(getControllerRmk((Api) annotation));
                }
            } catch (Exception e) {
                logger.info("error message,{}", e.getMessage());
            }
            return this;
        }

        private String getControllerRmk(Api api) {
            String controllerRmk = api.description();
            if (StringUtils.isEmpty(controllerRmk)) {
                controllerRmk = api.value();
            }
            if (StringUtils.isEmpty(controllerRmk)) {
                controllerRmk = api.produces();
            }
            if (StringUtils.isEmpty(controllerRmk)) {
                controllerRmk = Arrays.toString(api.tags());
            }
            return controllerRmk;
        }

        private Builder paramResult() {
            try {
                Map<String, Object> paramJson = Maps.newHashMap();
                Map<String, Object> autoParaJson = Maps.newHashMap();
                Map<String, Object> paramMap = Maps.newHashMap();
                for (ParamNameResolvableType paramNameType : apiInfo.getParamResolvableTypeList()) {
                    paramJson.putAll(BeanUtil.getParamJson(paramNameType.getParamName(), paramNameType.getParamType()));
                    paramMap.putAll(BeanUtil.getParamType(paramNameType.getParamName(), paramNameType.getParamType()));
                    autoParaJson.putAll(BeanUtil.getAutoParamType(paramNameType.getParamName(), paramNameType.getParamType()));
                }
                apiInfo.setParamApiJson(BeanUtil.getParamsApiJson(apiInfo.getParamResolvableTypeList()));
                apiInfo.setResultApiJson(BeanUtil.getApiResultJson(apiInfo.getResolvableType()));
                apiInfo.setTornaParamApiJson(apiInfo.getRequestType().equals(HttpMethod.GET.name())
                        ? BeanUtil.getTornaParamsApiJson(method, apiInfo.getParamResolvableTypeList())
                        : BeanUtil.postTornaParamsApiJson(apiInfo.getParamResolvableTypeList()));
                apiInfo.setTornaResultApiJson(BeanUtil.getTornaApiResultJson(apiInfo.getResolvableType()));
                apiInfo.setParamJson(JsonMapper.toJson(paramJson));
                apiInfo.setResultJson(BeanUtil.getResultJson(apiInfo.getResolvableType()));
                apiInfo.setAutoParamJson(JsonMapper.toJson(autoParaJson));
                apiInfo.setParamMap(paramMap);
            } catch (Exception e) {
                //异常放外层处理，便于debug查找问题原因
                logger.error("error info apiInfo：{}", JsonMapper.toJson(apiInfo));
                e.printStackTrace();
            }
            return this;
        }

        /**
         * 获取method参数信息
         */
        private Builder methodInfo() {
            List<String> paramNames = Arrays.asList(parameterUtil.getParameterNames(method));
            Class<?>[] paramTypes = method.getParameterTypes();
            setMethodRmk();
            setArgsArray(paramNames, method.getParameterTypes());
            apiInfo.setMethodParamType(getMethodParamType(paramTypes));
            argsStr = getArgsStr(paramNames, paramTypes);
            apiInfo.setArgsStr(argsStr);
            apiInfo.setResolvableType(ResolvableType.forMethodReturnType(method));
            apiInfo.setParamResolvableTypeList(buildParamResolvableTypeList(paramNames, paramTypes));
            return this;
        }

        private void setMethodRmk() {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                apiInfo.setMethodRmk(apiOperation.value());
            } else {
                apiInfo.setMethodRmk(MappingConstants.EMPTY_STRING);
            }
        }

        private void setArgsArray(List<String> paramNames, Class<?>[] paramTypes) {
            args = new Object[paramTypes.length];
            for (int i = 0; i < paramTypes.length; i++) {
                Map<String, String> map = Maps.newHashMap();
                map.put(paramNames.get(i), paramTypes[i].getTypeName());
                args[i] = map;
            }
        }

        private List<ParamNameResolvableType> buildParamResolvableTypeList(List<String> paramNames, Class<?>[] paramTypes) {
            List<ParamNameResolvableType> paramList = Lists.newArrayList();
            for (int i = 0; i < paramTypes.length; i++) {
                ParamNameResolvableType param = new ParamNameResolvableType();
                param.setParamName(paramNames.get(i));
                param.setParamType(ResolvableType.forMethodParameter(method, i));
                paramList.add(param);
            }
            return paramList;
        }

        private String getArgsStr(List<String> paramNames, Class<?>[] paramTypes) {
            StringBuffer argsBuffer = new StringBuffer();
            for (int i = 0; i < paramTypes.length; i++) {
                // 入参过滤Spring 内置参数
                Class paramClass = paramTypes[i];
                if (argsBuffer.length() == 0) {
                    argsBuffer.append(paramNames.get(i) + MappingConstants.EQ + paramClass.getTypeName());
                } else {
                    argsBuffer.append(MappingConstants.COM);
                    argsBuffer.append(paramNames.get(i) + MappingConstants.EQ + paramClass.getTypeName());
                }
            }
            return argsBuffer.toString();
        }

        private String getMethodParamType(Class<?>[] paramTypes) {
            StringBuffer stringBuffer = new StringBuffer();
            for (int i = 0; i < paramTypes.length; i++) {
                if (stringBuffer.length() == 0) {
                    stringBuffer.append(paramTypes[i].getTypeName());
                } else {
                    stringBuffer.append(MappingConstants.COM);
                    stringBuffer.append(paramTypes[i].getTypeName());
                }
            }
            return stringBuffer.toString();
        }

        public Builder buildCommon() {
            controllerInfo();
            methodInfo();
            paramResult();
            return this;
        }

        public ApiInfo build() {
            return apiInfo;
        }
    }
}